package com.hl.payServer.core.wePay;


import com.hl.payServer.exception.WepayException;
import com.hl.payServer.model.enums.TradeType;
import com.hl.payServer.model.enums.WepayField;
import com.hl.payServer.security.MD5;
import com.hl.payServer.util.Preconditions;
import com.hl.payServer.util.RandomStrs;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Date;
import java.util.Map;
import java.util.TreeMap;

/**
 * 支付组件
 */
public final class Pays extends Component {

    /**
     * 统一下单接口
     */
    private static final String PAY_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";

    /**
     * 刷卡支付接口
     */
    private static final String MICROPAY_URL = "https://api.mch.weixin.qq.com/pay/micropay";

    /**
     * 联图二维码转换
     */
    private static final String LIANTU_URL = "http://qr.liantu.core/api.php?text=";

    /**
     * 企业付款接口
     */
    private static final String COMPANY_PAY_URL = "https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers";

    /**
     * 关闭订单接口
     * 以下情况需要调用关单接口：商户订单支付失败需要生成新单号重新发起支付，要对原订单号调用关单，避免重复支付；
     * 系统下单后，用户支付超时，系统退出不再受理，避免用户继续，请调用关单接口。
     * 注意：订单生成后不能马上调用关单接口，最短调用时间间隔为5分钟。
     */
    private static final String CLOSE_ORDER_URL = "https://api.mch.weixin.qq.com/pay/closeorder";
    

    public Pays(Wepay wepay) {
        super(wepay);
    }


    /**
     * 刷卡支付
     * @param request 支付请求对象
     * @return 使用联图生成的二维码链接，或抛WepayException
     */
    public MicroPayResponse microPay(MicroPayRequest request){
        checkMicroPayParams(request);
        return doMicroPay(request, TradeType.MICROPAY);
    }

    /**
     * JS支付(公众号支付)
     * @param request 支付请求对象
     * @return JsPayResponse对象，或抛WepayException
     */
    public JsPayResponse jsPay(JsPayRequest request){
        checkJsPayParams(request);
        Map<String, Object> respData = doJsPay(request, TradeType.JSAPI);
        return buildJsPayResp(respData);
    }

    /**
     * 动态二维码支付(NATIVE)
     * @param request 支付请求对象
     * @return 使用联图生成的二维码链接，或抛WepayException
     */
    public Map<String, Object> qrPay(QrPayRequest request){
        checkQrPayParams(request);
        return doQrPay(request, TradeType.NATIVE);
    }

    /**
     * 动态二维码支付(NATIVE)
     * @param request 支付请求对象
     * @param convert 是否转换为二维码图片链接(使用联图)
     * @return 可访问的二维码链接，或抛WepayException
     */
    public String qrPay(QrPayRequest request, Boolean convert){
        checkQrPayParams(request);
        Map<String, Object> respData = doQrPay(request, TradeType.NATIVE);
        String codeUrl = String.valueOf(respData.get(WepayField.CODE_URL));
        if (convert){
            try {
                return LIANTU_URL + URLEncoder.encode(codeUrl, "UTF-8");
            } catch (UnsupportedEncodingException e) {
                throw new WepayException(e);
            }
        }
        return codeUrl;
    }

    /**
     * app支付
     * @param request 支付请求对象
     * @return AppPayResponse对象，或抛WepayException
     */
    public AppPayResponse appPay(PayRequest request){
        checkPayParams(request);
        Map<String, Object> respData = doAppPay(request, TradeType.APP);
        return buildAppPayResp(respData);
    }

    /**
     * JS支付
     * @param request 支付信息
     * @return 支付结果
     */
    private Map<String, Object> doJsPay(JsPayRequest request, TradeType tradeType){
        Map<String, String> payParams = buildPayParams(request);
        payParams.put(WepayField.OPEN_ID, request.getOpenId());
        putIfNotEmpty(payParams, WepayField.NOTIFY_URL, request.getNotifyUrl());
        putIfNotEmpty(payParams, WepayField.TRADE_TYPE, tradeType.type());
        return doPay(payParams);
    }

    /**
     * 刷卡支付
     * @param request 支付信息
     * @return 支付结果
     */
    private MicroPayResponse doMicroPay(MicroPayRequest request, TradeType tradeType){
        Map<String, String> payParams = buildPayParams(request);
        payParams.put(WepayField.AUTH_CODE, request.getAuthCode());
        return doMicroPay(payParams);
    }

    /**
     * APP支付
     * @param request 支付信
     * @return 支付结
     */
    private Map<String, Object> doAppPay(PayRequest request, TradeType tradeType){
        Map<String, String> payParams = buildPayParams(request);
        putIfNotEmpty(payParams, WepayField.NOTIFY_URL, request.getNotifyUrl());
        putIfNotEmpty(payParams, WepayField.TRADE_TYPE, tradeType.type());
        return doPay(payParams);
    }

    /**
     * 二维码支付
     * @param request 支付信息
     * @return 支付结果
     */
    private Map<String, Object> doQrPay(QrPayRequest request, TradeType tradeType){
        Map<String, String> payParams = buildPayParams(request);
        putIfNotEmpty(payParams, WepayField.PRODUCT_ID, request.getProductId());
        putIfNotEmpty(payParams, WepayField.NOTIFY_URL, request.getNotifyUrl());
        putIfNotEmpty(payParams, WepayField.TRADE_TYPE, tradeType.type());
        return doPay(payParams);
    }

    private Map<String, Object> doPay(Map<String, String> payParams) {
        buildSignParams(payParams);
        return doPost(PAY_URL, payParams);
    }

    private MicroPayResponse doMicroPay(Map<String, String> payParams) {
        buildSignParams(payParams);
        return doPost(MICROPAY_URL, payParams, MicroPayResponse.class);
    }

    private JsPayResponse buildJsPayResp(Map<String, Object> data) {
        String appId = wepay.getAppId();
        String nonceStr = RandomStrs.generate(16);
        String timeStamp = String.valueOf(new Date().getTime() / 1000);
        String pkg = WepayField.PREPAY_ID + "=" +
                data.get(WepayField.PREPAY_ID);
        //WepayField中的字段名不对，我们就直接拼一下得了
        String signing =
                "appId=" + appId +
                "&nonceStr=" + nonceStr +
                "&package=" + pkg +
                "&signType=MD5" +
                "&timeStamp=" + timeStamp +
                "&key=" + wepay.getAppKey();
        String signed = MD5.generate(signing, false).toUpperCase();
        return new JsPayResponse(appId, timeStamp, nonceStr, pkg, "MD5", signed);
    }

    private AppPayResponse buildAppPayResp(Map<String, Object> data) {
        System.out.print(data);
        String appId = wepay.getAppId();
        String partnerId= String.valueOf(data.get(WepayField.MCH_ID));
        String nonceStr = RandomStrs.generate(16);
        String timeStamp = String.valueOf(new Date().getTime() / 1000);
        String prepayId = String.valueOf(data.get(WepayField.PREPAY_ID));
        String signing =
                "appid=" + appId +
                        "&noncestr=" + nonceStr +
                        "&package=Sign=WXPay" +
                        "&partnerid=" + partnerId +
                        "&prepayid=" + prepayId +
                        //"&" + WepayField.SIGN_TYPE + "=MD5" +
                        "&timestamp=" + timeStamp +
                        "&" + WepayField.KEY + "=" + wepay.getAppKey();
/*        String signing =
                WepayField.APP_ID + "=" + appId +
                "&"+ WepayField.NONCE_STR +"=" + nonceStr +
                "&" + WepayField.PKG + "=Sign=WXPay" +
                "&" + WepayField.PARTNER_ID + "=" + partnerId +
                "&" + WepayField.PREPAY_ID + "=" + prepayId +
                //"&" + WepayField.SIGN_TYPE + "=MD5" +
                "&" + WepayField.TIME_STAMP + "=" + timeStamp +
                "&" + WepayField.KEY + "=" + wepay.getAppKey();*/
        String signed = MD5.generate(signing, false).toUpperCase();

        return new AppPayResponse(appId, partnerId, prepayId, timeStamp, nonceStr, signed);
    }

    /**
     * 检查支付参数合法性
     * @param request 支付请求对象
     */
    private void checkJsPayParams(JsPayRequest request) {
        checkPayParams(request);
        Preconditions.checkNotNullAndEmpty(request.getOpenId(), "openId");
    }

    private void checkMicroPayParams(MicroPayRequest request) {
        checkPayParams(request);
        Preconditions.checkNotNullAndEmpty(request.getAuthCode(), "authCode");
    }

    private void checkQrPayParams(QrPayRequest request) {
        checkPayParams(request);
        Preconditions.checkNotNullAndEmpty(request.getNotifyUrl(), "notifyUrl");
    }

    private void checkPayParams(PayRequest request) {
        Preconditions.checkNotNull(request, "pay detail can't be null");
        Preconditions.checkNotNullAndEmpty(request.getBody(), "body");
        Preconditions.checkNotNullAndEmpty(request.getOutTradeNo(), "outTradeNo");
        Integer totalFee = request.getTotalFee();
        Preconditions.checkArgument(totalFee != null && totalFee > 0, "totalFee must > 0");
        Preconditions.checkNotNullAndEmpty(request.getClientId(), "clientId");
        Preconditions.checkNotNull(request.getFeeType(), "feeType can't be null");
        Preconditions.checkNotNullAndEmpty(request.getTimeStart(), "timeStart");
    }

    /**
     * 构建公共支付参数
     * @param request 支付请求对象
     * @return 支付MAP参数
     */
    private Map<String, String> buildPayParams(PayRequest request) {
        Map<String, String> jsPayParams = new TreeMap<String,String>();

        // 配置参数
        buildConfigParams(jsPayParams);

        // 业务必需参数
        put(jsPayParams, WepayField.BODY, request.getBody());
        put(jsPayParams, WepayField.OUT_TRADE_NO, request.getOutTradeNo());
        put(jsPayParams, WepayField.TOTAL_FEE, request.getTotalFee() + "");
        put(jsPayParams, WepayField.SPBILL_CREATE_IP, request.getClientId());
        put(jsPayParams, WepayField.NOTIFY_URL, request.getNotifyUrl());
        put(jsPayParams, WepayField.FEE_TYPE, request.getFeeType().type());
        put(jsPayParams, WepayField.NONCE_STR, RandomStrs.generate(16));
        put(jsPayParams, WepayField.TIME_START, request.getTimeStart());

        // 业务可选参数
        putIfNotEmpty(jsPayParams, WepayField.DEVICE_INFO, request.getDeviceInfo());
        putIfNotEmpty(jsPayParams, WepayField.ATTACH, request.getAttach());
        putIfNotEmpty(jsPayParams, WepayField.DETAIL, request.getDetail());
        putIfNotEmpty(jsPayParams, WepayField.GOODS_TAG, request.getGoodsTag());
        putIfNotEmpty(jsPayParams, WepayField.TIME_EXPIRE, request.getTimeExpire());
        putIfNotEmpty(jsPayParams, WepayField.LIMIT_PAY, request.getLimitPay());

        return jsPayParams;
    }


    /**
     * 企业付款
     * @param params 支付请求参数
     * @return 
     */
    public CompanyPayResponse companyPay(Map params){
    	checkCompanyPayParams(params);
    	Map reqMap = new TreeMap();
    	put(reqMap, "mch_appid", wepay.getAppId());//微信分配的公众账号ID（企业号corpid即为此appId）
    	put(reqMap, "mchid", wepay.getMchId());//商户号
    	put(reqMap, "nonce_str", RandomStrs.generate(16));//随机字符串，不长于32位
    	put(reqMap, "spbill_create_ip", (String) params.get("clientId"));//调用接口的机器Ip地址
    	
    	
    	put(reqMap, "partner_trade_no", (String) params.get("outTradeNo"));//商户订单号，需保持唯一性
    	put(reqMap, "openid", (String) params.get("openid") );//用户openid    oI9rzjjHY78qAU2aglJFgTLtYUKg
    	put(reqMap, "check_name", (String) params.get("check_name") );//NO_CHECK：不校验真实姓名    FORCE_CHECK：强校验真实姓名（未实名认证的用户会校验失败，无法转账）   OPTION_CHECK：针对已实名认证的用户才校验真实姓名（未实名认证用户不校验，可以转账成功）
    	putIfNotEmpty(reqMap, "re_user_name", (String) params.get("re_user_name") );//收款用户姓名   可选
    	put(reqMap, "amount", params.get("amount") + "");//企业付款金额，单位为分
    	put(reqMap, "desc", (String) params.get("desc") );//企业付款操作说明信息。必填。
    	CompanyPayResponse respData = doCompanyPay(reqMap);
        return respData;
    }
    
    private CompanyPayResponse doCompanyPay(Map<String, String> payParams) {
        buildSignParams(payParams);
        return doHttpsPost(COMPANY_PAY_URL, payParams, CompanyPayResponse.class);
    }
    
    private void checkCompanyPayParams(Map map) {
		String check_name = (String) map.get("check_name");
		Preconditions.checkNotNull(map, "pay detail can't be null");
		Preconditions.checkNotNullAndEmpty((String) map.get("openid") , "openid");
		Preconditions.checkNotNullAndEmpty(check_name, "check_name");
		Preconditions.checkNotNullAndEmpty(String.valueOf(map.get("amount")) , "amount");
		Preconditions.checkNotNullAndEmpty((String) map.get("desc") , "desc");
		Integer amount = null;
		try {
			amount = (Integer) map.get("amount");
		} catch (Exception e) {
			Preconditions.checkArgument(false, "amount must is number");
		}
		Preconditions.checkArgument(amount != null && amount >= 100,"amount must >= 100");
		if (!"NO_CHECK".equals(check_name) && !"FORCE_CHECK".equals(check_name) && !"OPTION_CHECK".equals(check_name)) {
			Preconditions.checkArgument(false, "amount must >= 100");
		}
		if ("FORCE_CHECK".equals(check_name)) {
			Preconditions.checkNotNullAndEmpty((String) map.get("re_user_name") , "openid");
		}
    }
    
   
}
